package org.eclipse.gittab.player.internal.midi;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.gittab.gitTabDsl.GuitarStr;
import org.eclipse.gittab.gitTabDsl.Note;
import org.eclipse.gittab.gitTabDsl.TabModel;
import org.eclipse.gittab.gitTabDsl.Tuning;
import org.eclipse.gittab.util.TuningMapper;

public interface MidiMapper {

	/**
	 * Gets the MIDI number from a note. </p> Either the fret position is
	 * directly used or the fret position is calculated from the note name.
	 * 
	 * @param note
	 * @return
	 */
	public int getMidiFrom(Note note);

	public int getFretFrom(int midi, GuitarStr string);

	/**
	 * Gets the MIDI number from a note. </p> Either the fret position is
	 * directly used or the fret position is calculated from the note name.
	 * 
	 * @param note
	 * @return
	 */
	public int getMidiFrom(Tuning tuning, Note note);

	public interface TypeRegistry {
		/**
		 * Gets the instance that is acting as a cache for several
		 * {@link Tuning}s.
		 */
		public static final MidiMapper CACHE = new MidiMapperCache();
	}

	class MidiMapperCache extends
			HashMap<Tuning, Map<GuitarStr, List<Integer>>> implements
			MidiMapper {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1011979284682835731L;

		// list of MIDI numbers of all notes on all strings
		private Map<GuitarStr, List<Integer>> midiMap;

		/**
		 * Initializes this player with the given {@link Tuning} if not already
		 * contained in this cacche. Builds up the MIDI matrix and the note
		 * names.
		 */
		@Override
		public int getMidiFrom(Note note) {

			final Tuning tuning = Util.getContainerOfType(note, TabModel.class)
					.getTuning();

			return getMidiFrom(tuning, note);

		}

		@Override
		public int getMidiFrom(Tuning tuning, Note note) {

			if (!containsKey(tuning)) {

				buildMidiTable(tuning);
			}

			GuitarStr string = note.getGitString();

			List<Integer> midiList = get(tuning).get(string);

			TuningMapper mapper = TuningMapper.TypeRegistry.CACHE;

			int fretPos = mapper.getFret(tuning, note);

			if (fretPos != -1) {
				return midiList.get(fretPos);
			} else {
				// TODO log
				return 0;
			}
		}

		@Override
		public int getFretFrom(int midi, GuitarStr string) {

			final Tuning tuning = Util.getContainerOfType(string, Tuning.class);

			if (!containsKey(tuning)) {

				buildMidiTable(tuning);
			}

			return get(tuning).get(string).indexOf(midi);
		}

		private void buildMidiTable(Tuning tuning) {
			midiMap = new HashMap<GuitarStr, List<Integer>>();

			int frets = tuning.getFrets() + 1;
			// frets + 1 because also the open string makes a tone :)

			for (GuitarStr string : tuning.getStrings()) {

				List<Integer> midiList = new ArrayList<Integer>(frets);

				int midi = string.getMidi();
				for (int f = 0; f < frets; f++) {
					midiList.add(midi + f);
				}

				midiMap.put(string, midiList);
			}

			put(tuning, midiMap);
		}
	}
}
